package org.cheniy.domain.strategy.service.armory;

import org.cheniy.domain.strategy.model.entity.StrategyAwardEntity;
import org.cheniy.domain.strategy.model.entity.StrategyEntity;
import org.cheniy.domain.strategy.model.entity.StrategyRuleEntity;
import org.cheniy.domain.strategy.repository.IStrategyRepository;
import org.cheniy.types.common.Constants;
import org.cheniy.types.enums.ResponseCode;
import org.cheniy.types.exception.AppException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

/**
 * @author cheniy
 * @description 策略装配库(兵工厂)，负责初始化策略计算
 * @create 2025/3/1 23:22
 */
@Service
public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatch {

    @Resource
    private IStrategyRepository iStrategyRepository;

    /**
     * 根据活动策略id装配装配活动策略
     *
     * @param strategyId
     * @return
     */
    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        // 1. 查询策略配置
        List<StrategyAwardEntity> strategyAwardList = iStrategyRepository.queryStrategyAwardList(strategyId);

        // 2. 封装无权值的模式
        assembleComputeLotteryStrategy(String.valueOf(strategyId), strategyAwardList);

        // 3. 缓存奖品库存 【用于decr扣减库存使用】

        for (StrategyAwardEntity strategyAward : strategyAwardList) {
            Integer awardId = strategyAward.getAwardId();
            Integer awardCount = strategyAward.getAwardCount();
            // 将所有的商品库存存放在redis中
            cacheStrategyAwardCount(strategyId, awardId, awardCount);
        }


        // 3. 根据StrategyId查询出Strategy实体-获取实体规则
        StrategyEntity strategyEntity = iStrategyRepository.queryStrategyByStrategyId(strategyId);
        String ruleWeight = strategyEntity.getRuleWeight();
        if (null == ruleWeight) return true;

        // 4. 查询并封装是否有权重版本
        // 根据规则模型名称和StrategyId获取到规则表的实体
        StrategyRuleEntity strategyRuleEntity = iStrategyRepository.queryStrategyRule(strategyId, ruleWeight);
        if (null == strategyRuleEntity) {
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }
        Map<String, List<String>> ruleWeightMap = strategyRuleEntity.getRuleWeightValues();
        // 生成策略奖品实体的list（从无权值的list中克隆排除）
        Set<String> ruleWeightMapSet = ruleWeightMap.keySet();
        for (String mapKet : ruleWeightMapSet) {
            // 遍历每个键值对，获取值列表
            List<String> ruleWeightValues = ruleWeightMap.get(mapKet);
            ArrayList<StrategyAwardEntity> strategyAwardListClone = new ArrayList<>(strategyAwardList);
            // 删除strategyAwardList中getAwardId不在ruleWeightValues中的实体
            strategyAwardListClone.removeIf(entity -> !ruleWeightValues.contains(String.valueOf(entity.getAwardId())));

            // 进行计算装配，注意修改传入的参数，这里第一个参数用来拼接redis的key名称
            assembleComputeLotteryStrategy(String.valueOf(strategyId).concat("_").concat(mapKet), strategyAwardListClone);
        }

        return true;

    }

    @Override
    public boolean assembleLotteryStrategyByActivityId(Long activityId) {
        Long strategyId = iStrategyRepository.queryStrategyIdByActivityId(activityId);
        return assembleLotteryStrategy(strategyId);
    }


    /**
     * 拼接库存的redis的key，并将库存数量以.**_{strategyId}_awardId为key，库存数量为value的情况存放在redis中
     *
     * @param strategyId
     * @param awardId
     * @param awardCount
     */
    private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        iStrategyRepository.cacheStrategyAwardCount(cacheKey, awardCount);
    }


    public boolean assembleComputeLotteryStrategy(String strategyId, List<StrategyAwardEntity> strategyAwardList) {
        // 2. 获得最小的概率值
        BigDecimal minAwardRate = strategyAwardList.stream().map(StrategyAwardEntity::getAwardRate).min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);

        // 3. 获得概率总和
        BigDecimal totalAwardRate = strategyAwardList.stream().map(StrategyAwardEntity::getAwardRate).reduce(BigDecimal.ZERO, BigDecimal::add);

        // 4. 用1 % 0.0001 获取概率范围，百分位，千分位，万分位
        // RoundingMode.CEILING：向上取整
        BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);

        // 5. 生成策略奖品概率查找表【没有idx的概念，值对应奖品】
        ArrayList<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
        for (StrategyAwardEntity strategyAwardEntity : strategyAwardList) {
            // 查出每个奖品对于的个数，添加在list中，主要这里没有idx的概念
            Integer awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();
            for (int i = 0; i < rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue(); i++) {
                strategyAwardSearchRateTables.add(awardId);
            }
        }

        // 6. 将list进行乱序【对应的奖品数量不变，概率也相同】
        Collections.shuffle(strategyAwardSearchRateTables);
        // 7. 生成map集合
        HashMap<String, String> shuffleStrategyAwardSearchRateTables = new HashMap<>(rateRange.intValue());

        for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
            shuffleStrategyAwardSearchRateTables.put(String.valueOf(i), String.valueOf(strategyAwardSearchRateTables.get(i)));
        }


        // 8. 存放到redis中【集群中不可能每个app都需要执行这个方法，所以存在redis中给每个集群获取】
        iStrategyRepository.storeStrategyAwardSearchRateTable(strategyId, BigDecimal.valueOf(shuffleStrategyAwardSearchRateTables.size()), shuffleStrategyAwardSearchRateTables);

        return true;
    }

    /**
     * 根据策略id获得随机数范围获取奖品id
     *
     * @param strategyId
     * @return
     */
    @Override
    public Integer getRandomAwardId(Long strategyId) {

        // 获取到策略奖品的范围，用于获取到随机值的范围
        // 在集群模式中，
        Integer rateRange = iStrategyRepository.getRateRange(strategyId);


        // 生成随机数，获取奖品
        // new SecureRandom().nextInt(rateRange)： 根据rateRange的范围内生成随机数
        return iStrategyRepository.getAwardMapAwardIdByRandom(String.valueOf(strategyId), new SecureRandom().nextInt(rateRange));
    }

    /**
     * 根据策略id获得随机数范围获取奖品id-权重版
     *
     * @param strategyId
     * @param weightKey
     * @return
     */
    @Override
    public Integer getRandomAwardId(Long strategyId, String weightKey) {

        String key = String.valueOf(strategyId).concat("_").concat(weightKey);
        // 获取到策略奖品的范围，用于获取到随机值的范围
        // 在集群模式中，
        Integer rateRange = iStrategyRepository.getRateRange(key);
        // 生成随机数，获取奖品
        // new SecureRandom().nextInt(rateRange)： 根据rateRange的范围内生成随机数
        return iStrategyRepository.getAwardMapAwardIdByRandom(key, new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Boolean subtractionAwardStock(Long strategyId, Integer awardId, Date endDateTime) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return iStrategyRepository.subtractionAwardStock(cacheKey, endDateTime);
    }
}
